package net.zoneland.zrdp.framework.aspectj;

import java.util.*;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.fasterxml.jackson.core.JsonProcessingException;

import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.NamedThreadLocal;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;

import net.zoneland.zrdp.common.annotation.Log;
import net.zoneland.zrdp.common.enums.BusinessStatus;
import net.zoneland.zrdp.common.enums.HttpMethod;
import net.zoneland.zrdp.common.utils.JacksonUtils;
import net.zoneland.zrdp.common.utils.SecurityUtils;
import net.zoneland.zrdp.common.utils.ServletUtils;
import net.zoneland.zrdp.common.utils.StringUtilPlus;
import net.zoneland.zrdp.common.utils.ip.IpUtils;
import net.zoneland.zrdp.framework.manager.AsyncManager;
import net.zoneland.zrdp.framework.manager.factory.AsyncFactory;
import net.zoneland.zrdp.system.domain.SysOperLog;

/**
 * 操作日志记录处理
 *
 * @author zonevue
 */
@Aspect
@Component
public class LogAspect
{
    private static final Logger LOGGER = LoggerFactory.getLogger(LogAspect.class);

    /** 排除敏感属性字段 */
    protected static final String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" };

    /** 计算操作消耗时间 */
    private static final ThreadLocal<Long> TIME_THREADLOCAL = new NamedThreadLocal<>("Cost Time");
    private static final ThreadLocal<String> PARAM_THREADLOCAL = new NamedThreadLocal<>("Oper Param");

    private static final ThreadLocal<String> IP_THREADLOCAL = new NamedThreadLocal<>("Ip Param");
    private static final ThreadLocal<String> URI_THREADLOCAL = new NamedThreadLocal<>("uri Param");
    private static final ThreadLocal<String> METHOD_THREADLOCAL = new NamedThreadLocal<>("method Param");

    /**
     * 处理请求前执行
     */
    @Before(value = "@annotation(controllerLog)")
    public void boBefore(final JoinPoint joinPoint, final Log controllerLog) {
        final HttpServletRequest request = ServletUtils.getRequest();
        if (controllerLog.isSaveRequestData()) {
            PARAM_THREADLOCAL.set(getRequestValue(joinPoint, controllerLog, request));
        }
        IP_THREADLOCAL.set(IpUtils.getIpAddr());
        URI_THREADLOCAL.set(request.getRequestURI());
        METHOD_THREADLOCAL.set(request.getMethod());
        TIME_THREADLOCAL.set(System.currentTimeMillis());
    }

    /**
     * 处理完请求后执行
     *
     * @param joinPoint 切点
     */
    @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
    public void doAfterReturning(final JoinPoint joinPoint, final Log controllerLog, final Object jsonResult) {
        handleLog(joinPoint, controllerLog, null, jsonResult);
    }

    /**
     * 拦截异常操作
     *
     * @param joinPoint 切点
     * @param e 异常
     */
    @AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
    public void doAfterThrowing(final JoinPoint joinPoint, final Log controllerLog, final Exception e) {
        handleLog(joinPoint, controllerLog, e, null);
    }

    protected void handleLog(final JoinPoint joinPoint, final Log controllerLog, final Exception e, final Object jsonResult) {
        final SysOperLog operLog = new SysOperLog();


        try {
            // *========数据库日志=========*//
            operLog.setStatus(BusinessStatus.SUCCESS.ordinal());

            // 请求的地址
            operLog.setOperIp(IP_THREADLOCAL.get());
            operLog.setOperUrl(StringUtils.substring(URI_THREADLOCAL.get(), 0, 255));

            operLog.setOperName(SecurityUtils.getUserNameDefaultAnonymous());


            if (e != null) {
                operLog.setStatus(BusinessStatus.FAIL.ordinal());
                operLog.setErrorMsg(StringUtilPlus.truncateToValidLengthForMysql(e.getMessage()));
            }
            // 设置方法名称
            final String className = joinPoint.getTarget().getClass().getName();
            final String methodName = joinPoint.getSignature().getName();
            operLog.setMethod(className + "." + methodName + "()");
            // 设置请求方式
            operLog.setRequestMethod(METHOD_THREADLOCAL.get());
            // 处理设置注解上的参数
            getControllerMethodDescription(controllerLog, operLog, jsonResult);
            // 设置消耗时间
            operLog.setCostTime(System.currentTimeMillis() - TIME_THREADLOCAL.get());
            // 保存数据库
            AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
        } catch (final Exception exp) {
            // 记录本地异常日志
            try {
                LOGGER.error("异常信息:{}", JacksonUtils.toJsonString(operLog), exp);
            } catch (JsonProcessingException ex) {
                // 如果不能将 operLog 转换为 JSON，仅记录原始的异常
                LOGGER.error("转换operLog到JSON时出现异常", ex);
                LOGGER.error("原始异常信息", exp);
            }
        } finally {
            TIME_THREADLOCAL.remove();
            PARAM_THREADLOCAL.remove();
            IP_THREADLOCAL.remove();
            URI_THREADLOCAL.remove();
            METHOD_THREADLOCAL.remove();
        }
    }

    /**
     * 获取注解中对方法的描述信息 用于Controller层注解
     *
     * @param log 日志
     * @param operLog 操作日志
     * @param jsonResult 返回值
     * @throws JsonProcessingException json解析异常
     */
    private void getControllerMethodDescription(final Log log, final SysOperLog operLog,
                                               final Object jsonResult) throws JsonProcessingException {
        // 设置action动作
        operLog.setBusinessType(log.businessType().ordinal());
        // 设置标题
        operLog.setTitle(log.title());
        // 设置操作人类别
        operLog.setOperatorType(log.operatorType().ordinal());
        // 是否需要保存request，参数和值
        if (log.isSaveRequestData()) {
            // 获取参数的信息，传入到数据库中。
            operLog.setOperParam(PARAM_THREADLOCAL.get());
        }
        // 是否需要保存response，参数和值
        if (log.isSaveResponseData() && Objects.nonNull(jsonResult)) {
            operLog.setJsonResult(StringUtilPlus.truncateToValidLengthForMysql(JacksonUtils.toJsonString(jsonResult)));
        }
    }

    /**
     * 获取请求的参数，放到log中
     *
     * @param joinPoint 切点
     * @param log 注解相关
     *
     */
    private String getRequestValue(final JoinPoint joinPoint, final Log log, final HttpServletRequest request) {
        final Map<?, ?> paramsMap = ServletUtils.getParamMap(request);
        final String requestMethod = request.getMethod();
        final HttpMethod method = HttpMethod.valueOf(requestMethod);
        if (HttpMethod.PUT == method || HttpMethod.POST == method) {
            final String params = argsArrayToString(joinPoint.getArgs(), log.excludeParamNames());
            return StringUtilPlus.truncateToValidLengthForMysql(params);
        } else {
            try {
                final String jsonString = JacksonUtils.toJsonString(paramsMap);
                return StringUtilPlus.truncateToValidLengthForMysql(jsonString);
            } catch (final JsonProcessingException e) {
                final String servletPath = request.getServletPath();
                LOGGER.error("转json str 出错！{}", servletPath, e);
            }
        }
        return StringUtils.EMPTY;
    }

    /**
     * 参数拼装
     */

    private String argsArrayToString(final Object[] paramsArray, final String[] excludeParamNames) {
        if (paramsArray == null || paramsArray.length == 0) {
            return "";
        }

        return Arrays.stream(paramsArray)
            .filter(Objects::nonNull)
            .filter(param -> !isFilterObject(param))
            .map(this::objectToJson)
            .filter(Optional::isPresent)
            .map(Optional::get)
            .collect(Collectors.joining(" "));
    }

    private Optional<String> objectToJson(final Object o) {
        try {
            return Optional.of(JacksonUtils.toJsonString(o));
        } catch (final JsonProcessingException e) {
            LOGGER.error("object to json error", e);
            return Optional.empty();
        }
    }


    /**
     * 判断是否需要过滤的对象。
     *
     * @param o 对象信息。
     * @return 如果是需要过滤的对象，则返回true；否则返回false。
     */

    public boolean isFilterObject(final Object o) {
        if (o instanceof MultipartFile ||
            o instanceof HttpServletRequest ||
            o instanceof HttpServletResponse ||
            o instanceof BindingResult) {
            return true;
        }

        if (o.getClass().isArray()) {
            return containsInstanceOf((Object[]) o, MultipartFile.class);
        } else if (o instanceof Collection) {
            return containsInstanceOf((Collection<?>) o, MultipartFile.class);
        } else if (o instanceof Map) {
            return containsInstanceOf(((Map<?, ?>) o).values(), MultipartFile.class);
        }

        return false;
    }

    private boolean containsInstanceOf(final Collection<?> collection, final Class<?> clazz) {
        return collection.stream().anyMatch(clazz::isInstance);
    }

    private boolean containsInstanceOf(final Object[] array, final Class<?> clazz) {
        return Arrays.stream(array).anyMatch(clazz::isInstance);
    }

}
